{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "75e00695",
   "metadata": {},
   "source": [
    "# FAISS\n",
    "\n",
    "Facebook AI Similarity Search (Faiss)는 밀집 벡터의 효율적인 유사도 검색과 클러스터링을 위한 라이브러리입니다.\n",
    "\n",
    "Faiss는 RAM에 맞지 않을 수도 있는 벡터 집합을 포함하여 모든 크기의 벡터 집합을 검색하는 알고리즘을 포함하고 있습니다.\n",
    "\n",
    "또한 평가와 매개변수 튜닝을 위한 지원 코드도 포함되어 있습니다.\n",
    "\n",
    "**참고**\n",
    "- [LangChain FAISS 문서](https://python.langchain.com/v0.2/docs/integrations/vectorstores/faiss/)\n",
    "- [FAISS 문서](https://faiss.ai/)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "08231551",
   "metadata": {},
   "outputs": [],
   "source": [
    "# API 키를 환경변수로 관리하기 위한 설정 파일\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# API 키 정보 로드\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c1b8c6d3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# LangSmith 추적을 설정합니다. https://smith.langchain.com\n",
    "# !pip install langchain-teddynote\n",
    "from langchain_teddynote import logging\n",
    "\n",
    "# 프로젝트 이름을 입력합니다.\n",
    "logging.langsmith(\"CH09-VectorStores\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "99524ec6",
   "metadata": {},
   "source": [
    "샘플 데이터셋을 로드합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9c454b93",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_community.document_loaders import TextLoader\n",
    "from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
    "\n",
    "\n",
    "# 텍스트 분할\n",
    "text_splitter = RecursiveCharacterTextSplitter(chunk_size=600, chunk_overlap=0)\n",
    "\n",
    "# 텍스트 파일을 load -> List[Document] 형태로 변환\n",
    "loader1 = TextLoader(\"data/nlp-keywords.txt\")\n",
    "loader2 = TextLoader(\"data/finance-keywords.txt\")\n",
    "\n",
    "# 문서 분할\n",
    "split_doc1 = loader1.load_and_split(text_splitter)\n",
    "split_doc2 = loader2.load_and_split(text_splitter)\n",
    "\n",
    "# 문서 개수 확인\n",
    "len(split_doc1), len(split_doc2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aeb721ba",
   "metadata": {},
   "source": [
    "## VectorStore 생성"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c6246db",
   "metadata": {},
   "source": [
    "**주요 초기화 매개변수**\n",
    "\n",
    "1. 인덱싱 매개변수:\n",
    "   - `embedding_function` (Embeddings): 사용할 임베딩 함수\n",
    "\n",
    "2. 클라이언트 매개변수:\n",
    "   - `index` (Any): 사용할 FAISS 인덱스\n",
    "   - `docstore` (Docstore): 사용할 문서 저장소\n",
    "   - `index_to_docstore_id` (Dict[int, str]): 인덱스에서 문서 저장소 ID로의 매핑\n",
    "\n",
    "**참고**\n",
    "\n",
    "- FAISS는 고성능 벡터 검색 및 클러스터링을 위한 라이브러리입니다.\n",
    "- 이 클래스는 FAISS를 LangChain의 VectorStore 인터페이스와 통합합니다.\n",
    "- 임베딩 함수, FAISS 인덱스, 문서 저장소를 조합하여 효율적인 벡터 검색 시스템을 구축할 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "53810ba4",
   "metadata": {},
   "outputs": [],
   "source": [
    "import faiss\n",
    "from langchain_community.vectorstores import FAISS\n",
    "from langchain_community.docstore.in_memory import InMemoryDocstore\n",
    "from langchain_openai import OpenAIEmbeddings\n",
    "\n",
    "# 임베딩\n",
    "embeddings = OpenAIEmbeddings(model=\"text-embedding-3-small\")\n",
    "\n",
    "# 임베딩 차원 크기를 계산\n",
    "dimension_size = len(embeddings.embed_query(\"hello world\"))\n",
    "print(dimension_size)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ab6e86f5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# FAISS 벡터 저장소 생성\n",
    "db = FAISS(\n",
    "    embedding_function=embeddings,\n",
    "    index=faiss.IndexFlatL2(dimension_size),\n",
    "    docstore=InMemoryDocstore(),\n",
    "    index_to_docstore_id={},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cfffc156",
   "metadata": {},
   "source": [
    "### FAISS 벡터 저장소 생성 (from_documents)\n",
    "\n",
    "`from_documents` 클래스 메서드는 문서 리스트와 임베딩 함수를 사용하여 FAISS 벡터 저장소를 생성합니다.\n",
    "\n",
    "**매개변수**\n",
    "\n",
    "- `documents` (List[Document]): 벡터 저장소에 추가할 문서 리스트\n",
    "- `embedding` (Embeddings): 사용할 임베딩 함수\n",
    "- `**kwargs`: 추가 키워드 인자\n",
    "\n",
    "**동작 방식**\n",
    "\n",
    "1. 문서 리스트에서 텍스트 내용(`page_content`)과 메타데이터를 추출합니다.\n",
    "2. 추출한 텍스트와 메타데이터를 사용하여 `from_texts` 메서드를 호출합니다.\n",
    "\n",
    "**반환값**\n",
    "\n",
    "- `VectorStore`: 문서와 임베딩으로 초기화된 벡터 저장소 인스턴스\n",
    "\n",
    "**참고**\n",
    "\n",
    "- 이 메서드는 `from_texts` 메서드를 내부적으로 호출하여 벡터 저장소를 생성합니다.\n",
    "- 문서의 `page_content`는 텍스트로, `metadata`는 메타데이터로 사용됩니다.\n",
    "- 추가적인 설정이 필요한 경우 `kwargs`를 통해 전달할 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "48941175",
   "metadata": {},
   "outputs": [],
   "source": [
    "# DB 생성\n",
    "db = FAISS.from_documents(documents=split_doc1, embedding=OpenAIEmbeddings())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1a0273dd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 문서 저장소 ID 확인\n",
    "db.index_to_docstore_id"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8dbdda84",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 저장된 문서의 ID: Document 확인\n",
    "db.docstore._dict"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb2a529b",
   "metadata": {},
   "source": [
    "### FAISS 벡터 저장소 생성 (from_texts)\n",
    "\n",
    "`from_texts` 클래스 메서드는 텍스트 리스트와 임베딩 함수를 사용하여 FAISS 벡터 저장소를 생성합니다.\n",
    "\n",
    "**매개변수**\n",
    "\n",
    "- `texts` (List[str]): 벡터 저장소에 추가할 텍스트 리스트\n",
    "- `embedding` (Embeddings): 사용할 임베딩 함수\n",
    "- `metadatas` (Optional[List[dict]]): 메타데이터 리스트. 기본값은 None\n",
    "- `ids` (Optional[List[str]]): 문서 ID 리스트. 기본값은 None\n",
    "- `**kwargs`: 추가 키워드 인자\n",
    "\n",
    "**동작 방식**\n",
    "\n",
    "1. 제공된 임베딩 함수를 사용하여 텍스트를 임베딩합니다.\n",
    "2. 임베딩된 벡터와 함께 `__from` 메서드를 호출하여 FAISS 인스턴스를 생성합니다.\n",
    "\n",
    "**반환값**\n",
    "\n",
    "- `FAISS`: 생성된 FAISS 벡터 저장소 인스턴스\n",
    "\n",
    "**참고**\n",
    "\n",
    "- 이 메서드는 사용자 친화적인 인터페이스로, 문서 임베딩, 메모리 내 문서 저장소 생성, FAISS 데이터베이스 초기화를 한 번에 처리합니다.\n",
    "- 빠르게 시작하기 위한 편리한 방법입니다.\n",
    "\n",
    "**주의사항**\n",
    "\n",
    "- 대량의 텍스트를 처리할 때는 메모리 사용량에 주의해야 합니다.\n",
    "- 메타데이터나 ID를 사용하려면 텍스트 리스트와 동일한 길이의 리스트로 제공해야 합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a506c8a1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 문자열 리스트로 생성\n",
    "db2 = FAISS.from_texts(\n",
    "    [\"안녕하세요. 정말 반갑습니다.\", \"제 이름은 테디입니다.\"],\n",
    "    embedding=OpenAIEmbeddings(),\n",
    "    metadatas=[{\"source\": \"텍스트문서\"}, {\"source\": \"텍스트문서\"}],\n",
    "    ids=[\"doc1\", \"doc2\"],\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9a624f1c",
   "metadata": {},
   "source": [
    "저장된 결과를 확인합니다. id 값은 지정한 id 값이 잘 들어가 있는지 확인합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "68ff3a9a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 저장된 내용\n",
    "db2.docstore._dict"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "feab8888",
   "metadata": {},
   "source": [
    "### 유사도 검색 (Similarity Search)\n",
    "\n",
    "`similarity_search` 메서드는 주어진 쿼리와 가장 유사한 문서들을 검색하는 기능을 제공합니다.\n",
    "\n",
    "**매개변수**\n",
    "\n",
    "- `query` (str): 유사한 문서를 찾기 위한 검색 쿼리 텍스트\n",
    "- `k` (int): 반환할 문서 수. 기본값은 4\n",
    "- `filter` (Optional[Union[Callable, Dict[str, Any]]]): 메타데이터 필터링 함수 또는 딕셔너리. 기본값은 None\n",
    "- `fetch_k` (int): 필터링 전에 가져올 문서 수. 기본값은 20\n",
    "- `**kwargs`: 추가 키워드 인자\n",
    "\n",
    "**반환값**\n",
    "\n",
    "- `List[Document]`: 쿼리와 가장 유사한 문서 리스트\n",
    "\n",
    "**동작 방식**\n",
    "\n",
    "1. `similarity_search_with_score` 메서드를 내부적으로 호출하여 유사도 점수와 함께 문서를 검색합니다.\n",
    "2. 검색 결과에서 점수를 제외하고 문서만 추출하여 반환합니다.\n",
    "\n",
    "**주요 특징**\n",
    "\n",
    "- `filter` 매개변수를 사용하여 메타데이터 기반의 필터링이 가능합니다.\n",
    "- `fetch_k`를 통해 필터링 전 검색할 문서 수를 조절할 수 있어, 필터링 후 원하는 수의 문서를 확보할 수 있습니다.\n",
    "\n",
    "**사용 시 고려사항**\n",
    "\n",
    "- 검색 성능은 사용된 임베딩 모델의 품질에 크게 의존합니다.\n",
    "- 대규모 데이터셋에서는 `k`와 `fetch_k` 값을 적절히 조정하여 검색 속도와 정확도의 균형을 맞추는 것이 중요합니다.\n",
    "- 복잡한 필터링이 필요한 경우, `filter` 매개변수에 커스텀 함수를 전달하여 세밀한 제어가 가능합니다.\n",
    "\n",
    "**최적화 팁**\n",
    "\n",
    "- 자주 사용되는 쿼리에 대해서는 결과를 캐싱하여 반복적인 검색 속도를 향상시킬 수 있습니다.\n",
    "- `fetch_k`를 너무 크게 설정하면 검색 속도가 느려질 수 있으므로, 적절한 값을 실험적으로 찾는 것이 좋습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f92180e7",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 유사도 검색\n",
    "db.similarity_search(\"TF IDF 에 대하여 알려줘\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d827eaa7",
   "metadata": {},
   "source": [
    "`k` 값에 검색 결과의 개수를 지정할 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5adef4c5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# k 값 지정\n",
    "db.similarity_search(\"TF IDF 에 대하여 알려줘\", k=2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55b91f16",
   "metadata": {},
   "source": [
    "filter 에 metadata 정보를 활용하여 Filtering 할 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0c301730",
   "metadata": {},
   "outputs": [],
   "source": [
    "# filter 사용\n",
    "db.similarity_search(\n",
    "    \"TF IDF 에 대하여 알려줘\", filter={\"source\": \"data/nlp-keywords.txt\"}, k=2\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "93e12740",
   "metadata": {},
   "source": [
    "다음은 `filter` 에서 다른 `source` 를 사용하여 검색한 결과를 확인합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c4bef931",
   "metadata": {},
   "outputs": [],
   "source": [
    "# filter 사용\n",
    "db.similarity_search(\n",
    "    \"TF IDF 에 대하여 알려줘\", filter={\"source\": \"data/finance-keywords.txt\"}, k=2\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "54dbdfbc",
   "metadata": {},
   "source": [
    "### 문서(Document)로부터 추가 (add_documents)\n",
    "\n",
    "`add_documents` 메서드는 벡터 저장소에 문서를 추가하거나 업데이트하는 기능을 제공합니다.\n",
    "\n",
    "**매개변수**\n",
    "\n",
    "- `documents` (List[Document]): 벡터 저장소에 추가할 문서 리스트\n",
    "- `**kwargs`: 추가 키워드 인자\n",
    "\n",
    "**반환값**\n",
    "\n",
    "- `List[str]`: 추가된 텍스트의 ID 리스트\n",
    "\n",
    "**동작 방식**\n",
    "\n",
    "1. 문서에서 텍스트 내용과 메타데이터를 추출합니다.\n",
    "2. `add_texts` 메서드를 호출하여 실제 추가 작업을 수행합니다.\n",
    "\n",
    "**주요 특징**\n",
    "\n",
    "- 문서 객체를 직접 처리할 수 있어 편리합니다.\n",
    "- ID 처리 로직이 포함되어 있어 문서의 고유성을 보장합니다.\n",
    "- `add_texts` 메서드를 기반으로 동작하여 코드 재사용성을 높입니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0775d185",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.documents import Document\n",
    "\n",
    "# page_content, metadata 지정\n",
    "db.add_documents(\n",
    "    [\n",
    "        Document(\n",
    "            page_content=\"안녕하세요! 이번엔 도큐먼트를 새로 추가해 볼께요\",\n",
    "            metadata={\"source\": \"mydata.txt\"},\n",
    "        )\n",
    "    ],\n",
    "    ids=[\"new_doc1\"],\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4e9cf6f0",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 추가된 데이터를 확인\n",
    "db.similarity_search(\"안녕하세요\", k=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0d95bc27",
   "metadata": {},
   "source": [
    "### 텍스트로부터 추가 (add_texts)\n",
    "\n",
    "`add_texts` 메서드는 텍스트를 임베딩하고 벡터 저장소에 추가하는 기능을 제공합니다.\n",
    "\n",
    "**매개변수**\n",
    "\n",
    "- `texts` (Iterable[str]): 벡터 저장소에 추가할 텍스트 이터러블\n",
    "- `metadatas` (Optional[List[dict]]): 텍스트와 연관된 메타데이터 리스트 (선택적)\n",
    "- `ids` (Optional[List[str]]): 텍스트의 고유 식별자 리스트 (선택적)\n",
    "- `**kwargs`: 추가 키워드 인자\n",
    "\n",
    "**반환값**\n",
    "\n",
    "- `List[str]`: 벡터 저장소에 추가된 텍스트의 ID 리스트\n",
    "\n",
    "**동작 방식**\n",
    "\n",
    "1. 입력받은 텍스트 이터러블을 리스트로 변환합니다.\n",
    "2. `_embed_documents` 메서드를 사용하여 텍스트를 임베딩합니다.\n",
    "3. `__add` 메서드를 호출하여 임베딩된 텍스트를 벡터 저장소에 추가합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fcc3705d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 신규 데이터를 추가\n",
    "db.add_texts(\n",
    "    [\"이번엔 텍스트 데이터를 추가합니다.\", \"추가한 2번째 텍스트 데이터 입니다.\"],\n",
    "    metadatas=[{\"source\": \"mydata.txt\"}, {\"source\": \"mydata.txt\"}],\n",
    "    ids=[\"new_doc2\", \"new_doc3\"],\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e91972ae",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 추가된 데이터를 확인\n",
    "db.index_to_docstore_id"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b956df3b",
   "metadata": {},
   "source": [
    "### 문서 삭제 (Delete Documents)\n",
    "\n",
    "`delete` 메서드는 벡터 저장소에서 지정된 ID에 해당하는 문서를 삭제하는 기능을 제공합니다.\n",
    "\n",
    "**매개변수**\n",
    "\n",
    "- `ids` (Optional[List[str]]): 삭제할 문서의 ID 리스트\n",
    "- `**kwargs`: 추가 키워드 인자 (이 메서드에서는 사용되지 않음)\n",
    "\n",
    "**반환값**\n",
    "\n",
    "- `Optional[bool]`: 삭제 성공 시 True, 실패 시 False, 구현되지 않은 경우 None\n",
    "\n",
    "**동작 방식**\n",
    "\n",
    "1. 입력된 ID의 유효성을 검사합니다.\n",
    "2. 삭제할 ID에 해당하는 인덱스를 찾습니다.\n",
    "3. FAISS 인덱스에서 해당 ID를 제거합니다.\n",
    "4. 문서 저장소에서 해당 ID의 문서를 삭제합니다.\n",
    "5. 인덱스와 ID 매핑을 업데이트합니다.\n",
    "\n",
    "**주요 특징**\n",
    "\n",
    "- ID 기반 삭제로 정확한 문서 관리가 가능합니다.\n",
    "- FAISS 인덱스와 문서 저장소 양쪽에서 삭제를 수행합니다.\n",
    "- 삭제 후 인덱스 재정렬을 통해 데이터 일관성을 유지합니다.\n",
    "\n",
    "**주의사항**\n",
    "\n",
    "- 삭제 작업은 되돌릴 수 없으므로 신중하게 수행해야 합니다.\n",
    "- 동시성 제어가 구현되어 있지 않아 다중 스레드 환경에서 주의가 필요합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "49fcc212",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 삭제용 데이터를 추가\n",
    "ids = db.add_texts(\n",
    "    [\"삭제용 데이터를 추가합니다.\", \"2번째 삭제용 데이터입니다.\"],\n",
    "    metadatas=[{\"source\": \"mydata.txt\"}, {\"source\": \"mydata.txt\"}],\n",
    "    ids=[\"delete_doc1\", \"delete_doc2\"],\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e8b6af52",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 삭제할 id 를 확인\n",
    "print(ids)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a4269c3a",
   "metadata": {},
   "source": [
    "`delete` 는 ids 를 입력하여 삭제할 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3574affb",
   "metadata": {},
   "outputs": [],
   "source": [
    "# id 로 삭제\n",
    "db.delete(ids)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c9775d9a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 삭제된 결과를 출력\n",
    "db.index_to_docstore_id"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2628b163",
   "metadata": {},
   "source": [
    "## 저장 및 로드\n",
    "\n",
    "### 로컬 저장 (Save Local)\n",
    "\n",
    "`save_local` 메서드는 FAISS 인덱스, 문서 저장소, 그리고 인덱스-문서 ID 매핑을 로컬 디스크에 저장하는 기능을 제공합니다.\n",
    "\n",
    "**매개변수**\n",
    "\n",
    "- `folder_path` (str): 저장할 폴더 경로\n",
    "- `index_name` (str): 저장할 인덱스 파일 이름 (기본값: \"index\")\n",
    "\n",
    "**동작 방식**\n",
    "\n",
    "1. 지정된 폴더 경로를 생성합니다 (이미 존재하는 경우 무시).\n",
    "2. FAISS 인덱스를 별도의 파일로 저장합니다.\n",
    "3. 문서 저장소와 인덱스-문서 ID 매핑을 pickle 형식으로 저장합니다.\n",
    "\n",
    "**사용 시 고려사항**\n",
    "\n",
    "- 저장 경로에 대한 쓰기 권한이 필요합니다.\n",
    "- 대용량 데이터의 경우 저장 공간과 시간이 상당히 소요될 수 있습니다.\n",
    "- pickle 사용으로 인한 보안 위험을 고려해야 합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "980707f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 로컬 Disk 에 저장\n",
    "db.save_local(folder_path=\"faiss_db\", index_name=\"faiss_index\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "407e3b36",
   "metadata": {},
   "source": [
    "### 로컬에서 불러오기 (Load Local)\n",
    "\n",
    "`load_local` 클래스 메서드는 로컬 디스크에 저장된 FAISS 인덱스, 문서 저장소, 그리고 인덱스-문서 ID 매핑을 불러오는 기능을 제공합니다.\n",
    "\n",
    "**매개변수**\n",
    "\n",
    "- `folder_path` (str): 불러올 파일들이 저장된 폴더 경로\n",
    "- `embeddings` (Embeddings): 쿼리 생성에 사용할 임베딩 객체\n",
    "- `index_name` (str): 불러올 인덱스 파일 이름 (기본값: \"index\")\n",
    "- `allow_dangerous_deserialization` (bool): pickle 파일 역직렬화 허용 여부 (기본값: False)\n",
    "\n",
    "**반환값**\n",
    "\n",
    "- `FAISS`: 로드된 FAISS 객체\n",
    "\n",
    "**동작 방식**\n",
    "\n",
    "1. 역직렬화의 위험성을 확인하고 사용자의 명시적 허가를 요구합니다.\n",
    "2. FAISS 인덱스를 별도로 불러옵니다.\n",
    "3. pickle을 사용하여 문서 저장소와 인덱스-문서 ID 매핑을 불러옵니다.\n",
    "4. 불러온 데이터로 FAISS 객체를 생성하여 반환합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5241fbe3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 저장된 데이터를 로드\n",
    "loaded_db = FAISS.load_local(\n",
    "    folder_path=\"faiss_db\",\n",
    "    index_name=\"faiss_index\",\n",
    "    embeddings=embeddings,\n",
    "    allow_dangerous_deserialization=True,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "84512ed3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 로드된 데이터를 확인\n",
    "loaded_db.index_to_docstore_id"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "66c172dc",
   "metadata": {},
   "source": [
    "### FAISS 객체 병합 (Merge From)\n",
    "\n",
    "`merge_from` 메서드는 현재 FAISS 객체에 다른 FAISS 객체를 병합하는 기능을 제공합니다.\n",
    "\n",
    "**매개변수**\n",
    "\n",
    "- `target` (FAISS): 현재 객체에 병합할 대상 FAISS 객체\n",
    "\n",
    "**동작 방식**\n",
    "\n",
    "1. 문서 저장소의 병합 가능 여부를 확인합니다.\n",
    "2. 기존 인덱스의 길이를 기준으로 새로운 문서들의 인덱스를 설정합니다.\n",
    "3. FAISS 인덱스를 병합합니다.\n",
    "4. 대상 FAISS 객체의 문서와 ID 정보를 추출합니다.\n",
    "5. 추출한 정보를 현재 문서 저장소와 인덱스-문서 ID 매핑에 추가합니다.\n",
    "\n",
    "**주요 특징**\n",
    "\n",
    "- 두 FAISS 객체의 인덱스, 문서 저장소, 인덱스-문서 ID 매핑을 모두 병합합니다.\n",
    "- 인덱스 번호의 연속성을 유지하면서 병합합니다.\n",
    "- 문서 저장소의 병합 가능 여부를 사전에 확인합니다.\n",
    "\n",
    "\n",
    "**주의사항**\n",
    "\n",
    "- 병합 대상 FAISS 객체와 현재 객체의 구조가 호환되어야 합니다.\n",
    "- 중복 ID 처리에 주의해야 합니다. 현재 구현에서는 중복 검사를 하지 않습니다.\n",
    "- 병합 과정에서 예외가 발생하면 부분적으로 병합된 상태가 될 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bbc2cef3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 저장된 데이터를 로드\n",
    "db = FAISS.load_local(\n",
    "    folder_path=\"faiss_db\",\n",
    "    index_name=\"faiss_index\",\n",
    "    embeddings=embeddings,\n",
    "    allow_dangerous_deserialization=True,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af8695e5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 새로운 FAISS 벡터 저장소 생성\n",
    "db2 = FAISS.from_documents(documents=split_doc2, embedding=OpenAIEmbeddings())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "05065f4f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# db 의 데이터 확인\n",
    "db.index_to_docstore_id"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fb349620",
   "metadata": {},
   "outputs": [],
   "source": [
    "# db2 의 데이터 확인\n",
    "db2.index_to_docstore_id"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "467641e1",
   "metadata": {},
   "source": [
    "`merge_from` 를 사용하여 2개의 db 를 병합합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4992feb3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# db + db2 를 병합\n",
    "db.merge_from(db2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "03474d4a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 병합된 데이터 확인\n",
    "db.index_to_docstore_id"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e68bf0aa",
   "metadata": {},
   "source": [
    "## 검색기로 변환 (as_retriever)\n",
    "\n",
    "`as_retriever` 메서드는 현재 벡터 저장소를 기반으로 `VectorStoreRetriever` 객체를 생성하는 기능을 제공합니다.\n",
    "\n",
    "**매개변수**\n",
    "\n",
    "- `**kwargs`: 검색 함수에 전달할 키워드 인자\n",
    "  - `search_type` (Optional[str]): 검색 유형 (`\"similarity\"`, `\"mmr\"`, `\"similarity_score_threshold\"`)\n",
    "  - `search_kwargs` (Optional[Dict]): 검색 함수에 전달할 추가 키워드 인자\n",
    "\n",
    "**반환값**\n",
    "\n",
    "- `VectorStoreRetriever`: 벡터 저장소 기반의 검색기 객체\n",
    "\n",
    "**주요 기능**\n",
    "\n",
    "1. 다양한 검색 유형 지원:\n",
    "   - `\"similarity\"`: 유사도 기반 검색 (기본값)\n",
    "   - `\"mmr\"`: Maximal Marginal Relevance 검색\n",
    "   - `\"similarity_score_threshold\"`: 임계값 기반 유사도 검색\n",
    "\n",
    "2. 검색 매개변수 커스터마이징:\n",
    "   - `k`: 반환할 문서 수\n",
    "   - `score_threshold`: 유사도 점수 임계값\n",
    "   - `fetch_k`: MMR 알고리즘에 전달할 문서 수\n",
    "   - `lambda_mult`: MMR 다양성 조절 파라미터\n",
    "   - `filter`: 문서 메타데이터 기반 필터링\n",
    "\n",
    "**사용 시 고려사항**\n",
    "\n",
    "- 검색 유형과 매개변수를 적절히 선택하여 검색 결과의 품질과 다양성을 조절할 수 있습니다.\n",
    "- 대규모 데이터셋에서는 `fetch_k`와 `k` 값을 조절하여 성능과 정확도의 균형을 맞출 수 있습니다.\n",
    "- 필터링 기능을 활용하여 특정 조건에 맞는 문서만 검색할 수 있습니다.\n",
    "\n",
    "**최적화 팁**\n",
    "\n",
    "- MMR 검색 시 `fetch_k`를 높이고 `lambda_mult`를 조절하여 다양성과 관련성의 균형을 맞출 수 있습니다.\n",
    "- 임계값 기반 검색을 사용하여 높은 관련성을 가진 문서만 반환할 수 있습니다.\n",
    "\n",
    "**주의사항**\n",
    "\n",
    "- 부적절한 매개변수 설정은 검색 성능이나 결과의 품질에 영향을 줄 수 있습니다.\n",
    "- 대규모 데이터셋에서 높은 `k` 값 설정은 검색 시간을 증가시킬 수 있습니다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e46fbe7",
   "metadata": {},
   "source": [
    "기본 값으로 설정된 4개 문서를 유사도 검색을 수행하여 조회합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "679f8f29",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 새로운 FAISS 벡터 저장소 생성\n",
    "db = FAISS.from_documents(\n",
    "    documents=split_doc1 + split_doc2, embedding=OpenAIEmbeddings()\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d3820dd8",
   "metadata": {},
   "source": [
    "기본 검색기(retriever) 는 4개의 문서를 반환합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b8e61489",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 검색기로 변환\n",
    "retriever = db.as_retriever()\n",
    "# 검색 수행\n",
    "retriever.invoke(\"Word2Vec 에 대하여 알려줘\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d115fccc",
   "metadata": {},
   "source": [
    "다양성이 높은 더 많은 문서 검색\n",
    "\n",
    "- `k`: 반환할 문서 수 (기본값: 4)\n",
    "- `fetch_k`: MMR 알고리즘에 전달할 문서 수 (기본값: 20)\n",
    "- `lambda_mult`: MMR 결과의 다양성 조절 (0~1, 기본값: 0.5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3925cf6f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# MMR 검색 수행\n",
    "retriever = db.as_retriever(\n",
    "    search_type=\"mmr\", search_kwargs={\"k\": 6, \"lambda_mult\": 0.25, \"fetch_k\": 10}\n",
    ")\n",
    "retriever.invoke(\"Word2Vec 에 대하여 알려줘\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d284c095",
   "metadata": {},
   "source": [
    "MMR 알고리즘을 위해 더 많은 문서를 가져오되 상위 2개만 반환"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "59f83521",
   "metadata": {},
   "outputs": [],
   "source": [
    "# MMR 검색 수행, 상위 2개만 반환\n",
    "retriever = db.as_retriever(search_type=\"mmr\", search_kwargs={\"k\": 2, \"fetch_k\": 10})\n",
    "retriever.invoke(\"Word2Vec 에 대하여 알려줘\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e0ce026f",
   "metadata": {},
   "source": [
    "특정 임계값 이상의 유사도를 가진 문서만 검색"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b9d99b87",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 임계값 기반 검색 수행\n",
    "retriever = db.as_retriever(\n",
    "    search_type=\"similarity_score_threshold\", search_kwargs={\"score_threshold\": 0.8}\n",
    ")\n",
    "\n",
    "retriever.invoke(\"Word2Vec 에 대하여 알려줘\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0b8d48f4",
   "metadata": {},
   "source": [
    "가장 유사한 단일 문서만 검색"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d353f737",
   "metadata": {},
   "outputs": [],
   "source": [
    "# k=1 로 설정하여 가장 유사한 문서만 검색\n",
    "retriever = db.as_retriever(search_kwargs={\"k\": 1})\n",
    "\n",
    "retriever.invoke(\"Word2Vec 에 대하여 알려줘\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "209c12a0",
   "metadata": {},
   "source": [
    "특정 메타데이터 필터 적용"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d20bdd98",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 메타데이터 필터 적용\n",
    "retriever = db.as_retriever(\n",
    "    search_kwargs={\"filter\": {\"source\": \"data/finance-keywords.txt\"}, \"k\": 2}\n",
    ")\n",
    "retriever.invoke(\"ESG 에 대하여 알려줘\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cd7dd5aa",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "py-test",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
